<?php
require_once ('Zend/Auth/Adapter/Interface.php');

/**
 * Authentication adapter for phpBB.
 * 
 * Hashing functions from phpBB.
 * 
 * @author Florian Sonner
 */
class GeoGebra_Auth_Adapter_Forum implements Zend_Auth_Adapter_Interface {
	private $_username;
	private $_password;
	private $_data;
	
	/**
	 * Construct adapter.
	 */
	public function __construct($username, $password) {
		$this->_username = $username;
		$this->_password = $password;
	}
	
	/**
	 * Authenticate the user.
	 * 
	 * @see Zend_Auth_Adapter_Interface::authenticate()
	 */
	public function authenticate() {
		require_once 'models/ForumUserModel.php';
		$userModel = new ForumUserModel();
		
		$user = $userModel->fetchByUsername($this->_username);
		
		if(empty($user)) {
			return new Zend_Auth_Result(
				Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,
				null,
				array('No user with this username found.')
			);
		}
		
		$options = Zend_Registry::get('phpBB');
		
		// exceeded login attemps, ask for authentication in forum and block
		if($options['maxloginattemps'] > 0 && $user->user_login_attempts >= $options['maxloginattemps']) {
			return new Zend_Auth_Result(
				Zend_Auth_Result::FAILURE, 
				null,
				array('Maximum login attempts exceeded')
			);
		}
		
		// need to convert password, can't be done here, block
		if($user->user_pass_convert) {
			return new Zend_Auth_Result(
				Zend_Auth_Result::FAILURE, 
				null,
				array('Old password. Can\'t be converted here.')
			);
		}
		
		// password correct?
		if($this->checkHash($this->_password, $user->user_password)) {
			
			// reset attempt count
			if($user->user_login_attempts != 0) {
				$userModel->resetLoginAttempts($user->user_id);
			}
			
			$this->_data = array('id' => $user->user_id, 'email' => $user->user_email); 
			
			return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, 'forum:'.$user->user_id);
		} 
		
		// password incorrect..
		
		$userModel->increaseLoginAttempts($user->user_id);
		
		return new Zend_Auth_Result(
			Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, 
			null,
			array('Password does not match username.')
		);
	}
	
	public function getData() {
		return $this->_data;
	}
	
	// from phpBB
	private function checkHash($password, $hash) {
		$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
		if (strlen($hash) == 34)
		{
			return ($this->hashCryptPrivate($password, $hash, $itoa64) === $hash) ? true : false;
		}
	
		return (md5($password) === $hash) ? true : false;
	}
	
	// from phpBB
	private function hashCryptPrivate($password, $setting, &$itoa64)
	{
		$output = '*';
	
		// Check for correct hash
		if (substr($setting, 0, 3) != '$H$')
		{
			return $output;
		}
	
		$count_log2 = strpos($itoa64, $setting[3]);
	
		if ($count_log2 < 7 || $count_log2 > 30)
		{
			return $output;
		}
	
		$count = 1 << $count_log2;
		$salt = substr($setting, 4, 8);
	
		if (strlen($salt) != 8)
		{
			return $output;
		}
	
		if (PHP_VERSION >= 5)
		{
			$hash = md5($salt . $password, true);
			do
			{
				$hash = md5($hash . $password, true);
			}
			while (--$count);
		}
		else
		{
			$hash = pack('H*', md5($salt . $password));
			do
			{
				$hash = pack('H*', md5($hash . $password));
			}
			while (--$count);
		}
	
		$output = substr($setting, 0, 12);
		$output .= $this->hashEncode64($hash, 16, $itoa64);
	
		return $output;
	}
	
	private function hashEncode64($input, $count, &$itoa64)
	{
		$output = '';
		$i = 0;
	
		do
		{
			$value = ord($input[$i++]);
			$output .= $itoa64[$value & 0x3f];
	
			if ($i < $count)
			{
				$value |= ord($input[$i]) << 8;
			}
	
			$output .= $itoa64[($value >> 6) & 0x3f];
	
			if ($i++ >= $count)
			{
				break;
			}
	
			if ($i < $count)
			{
				$value |= ord($input[$i]) << 16;
			}
	
			$output .= $itoa64[($value >> 12) & 0x3f];
	
			if ($i++ >= $count)
			{
				break;
			}
	
			$output .= $itoa64[($value >> 18) & 0x3f];
		}
		while ($i < $count);
	
		return $output;
	}
}